/******************************************************************************* * Copyright (c) 2015 GK Software AG and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Stephan Herrmann - Initial API and implementation *******************************************************************************/ package org.eclipse.jdt.internal.corext.fix; import static org.eclipse.jdt.core.util.ExternalAnnotationUtil.NONNULL; import static org.eclipse.jdt.core.util.ExternalAnnotationUtil.NO_ANNOTATION; import static org.eclipse.jdt.core.util.ExternalAnnotationUtil.NULLABLE; import static org.eclipse.jdt.core.util.ExternalAnnotationUtil.annotateMember; import static org.eclipse.jdt.core.util.ExternalAnnotationUtil.annotateMethodParameterType; import static org.eclipse.jdt.core.util.ExternalAnnotationUtil.annotateMethodReturnType; import static org.eclipse.jdt.core.util.ExternalAnnotationUtil.extractGenericSignature; import static org.eclipse.jdt.core.util.ExternalAnnotationUtil.extractGenericTypeSignature; import static org.eclipse.jdt.core.util.ExternalAnnotationUtil.getAnnotationFile; import static org.eclipse.jdt.internal.ui.text.spelling.WordCorrectionProposal.getHtmlRepresentation; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.List; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Path; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IWorkspaceRoot; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.contentassist.IContextInformation; import org.eclipse.jdt.core.IClasspathAttribute; import org.eclipse.jdt.core.IClasspathEntry; import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IPackageFragmentRoot; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.compiler.IProblem; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.ASTVisitor; import org.eclipse.jdt.core.dom.ArrayType; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.Dimension; import org.eclipse.jdt.core.dom.FieldDeclaration; import org.eclipse.jdt.core.dom.IMethodBinding; import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.IVariableBinding; import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.ParameterizedType; import org.eclipse.jdt.core.dom.PrimitiveType; import org.eclipse.jdt.core.dom.SimpleType; import org.eclipse.jdt.core.dom.SingleVariableDeclaration; import org.eclipse.jdt.core.dom.StructuralPropertyDescriptor; import org.eclipse.jdt.core.dom.Type; import org.eclipse.jdt.core.dom.TypeParameter; import org.eclipse.jdt.core.dom.VariableDeclaration; import org.eclipse.jdt.core.dom.VariableDeclarationFragment; import org.eclipse.jdt.core.dom.WildcardType; import org.eclipse.jdt.core.util.ExternalAnnotationUtil; import org.eclipse.jdt.core.util.ExternalAnnotationUtil.MergeStrategy; import org.eclipse.jdt.internal.corext.dom.ASTNodes; import org.eclipse.jdt.internal.corext.util.JavaModelUtil; import org.eclipse.jdt.internal.corext.util.Messages; import org.eclipse.jdt.ui.text.java.IJavaCompletionProposal; import org.eclipse.jdt.ui.text.java.correction.ICommandAccess; import org.eclipse.jdt.internal.ui.JavaPlugin; import org.eclipse.jdt.internal.ui.JavaPluginImages; import org.eclipse.jdt.internal.ui.JavaUIStatus; import org.eclipse.jdt.internal.ui.text.correction.ExternalNullAnnotationQuickAssistProcessor; import org.eclipse.jdt.internal.ui.text.correction.IProposalRelevance; /** * Proposals for null annotations that modify external annotations, rather than Java source files. * * @see <a href="https://bugs.eclipse.org/458200">[null] "Annotate" proposals for adding external * null annotations to library classes</a> * @since 3.11 */ public class ExternalNullAnnotationChangeProposals { static final String CONSTRUCTOR_SELECTOR= "<init>"; //$NON-NLS-1$ static abstract class SignatureAnnotationChangeProposal implements IJavaCompletionProposal, ICommandAccess { protected String fLabel; protected ICompilationUnit fCU; // cu where the assist was invoked protected String fAffectedTypeName; protected IFile fAnnotationFile; protected String fSelector; protected String fSignature; protected String fCurrentAnnotated; protected String fAnnotatedSignature; protected MergeStrategy fMergeStrategy; protected String[] fDryRun; // result from a dry-run signature update; structure: { prefix, old-type, new-type, postfix } /* return true if the operation is available. */ protected boolean initializeOperation(ICompilationUnit cu, ITypeBinding declaringClass, String selector, String plainSignature, String annotatedSignature, String label, MergeStrategy mergeStrategy) { IJavaProject project= (IJavaProject) cu.getAncestor(IJavaElement.JAVA_PROJECT); IFile file= null; try { file= getAnnotationFile(project, declaringClass, new NullProgressMonitor()); } catch (CoreException e) { return false; } if (file == null) return false; fCU= cu; fAffectedTypeName= declaringClass.getErasure().getBinaryName().replace('.', '/'); fAnnotationFile= file; fSelector= selector; fAnnotatedSignature= annotatedSignature; fSignature= plainSignature; fLabel= label; fMergeStrategy= mergeStrategy; fCurrentAnnotated= ExternalAnnotationUtil.getAnnotatedSignature(fAffectedTypeName, file, fSelector, fSignature); if (fCurrentAnnotated == null) fCurrentAnnotated= fSignature; dryRun(); return fDryRun != null && !fDryRun[1].equals(fDryRun[2]); } /** * Perform a dry-run annotation update, to check if we have any update, indeed. If * successful, the result should be available in {@link #fDryRun}. */ protected abstract void dryRun(); @Override public Point getSelection(IDocument document) { return null; // nothing to reveal in the current editor. } @Override public String getDisplayString() { return fLabel; } @Override public Image getImage() { return JavaPluginImages.get(JavaPluginImages.IMG_OBJS_ANNOTATION); } @Override public IContextInformation getContextInformation() { return null; } @Override public void apply(IDocument document) { try { doAnnotateMember(new NullProgressMonitor()); } catch (CoreException e) { JavaPlugin.log(e); } catch (IOException e) { JavaPlugin.log(e); } } @Override public int getRelevance() { return IProposalRelevance.CHANGE_METHOD; } @Override public String getCommandId() { return ExternalNullAnnotationQuickAssistProcessor.ANNOTATE_MEMBER_ID; } @Override public String getAdditionalProposalInfo() { StringBuffer buffer= new StringBuffer(); buffer.append("<dl>"); //$NON-NLS-1$ buffer.append("<dt>").append(getHtmlRepresentation(fSelector)).append("</dt>"); //$NON-NLS-1$ //$NON-NLS-2$ buffer.append("<dd>").append(getHtmlRepresentation(fSignature)).append("</dd>"); //$NON-NLS-1$ //$NON-NLS-2$ buffer.append("<dd>").append(getFullAnnotatedSignatureHTML()).append("</dd>"); //$NON-NLS-1$ //$NON-NLS-2$ buffer.append("</dl>"); //$NON-NLS-1$ return buffer.toString(); } protected String getFullAnnotatedSignatureHTML() { String[] parts= fDryRun; // search the difference: int pos= 0; while (pos < parts[1].length() && pos < parts[2].length()) { if (parts[1].charAt(pos) != parts[2].charAt(pos)) break; pos++; } // prefix up-to the difference: StringBuilder buf= new StringBuilder(); buf.append(getHtmlRepresentation(parts[0])); buf.append(getHtmlRepresentation(parts[2].substring(0, pos))); // highlight the difference: switch (parts[2].charAt(pos)) { case NULLABLE: case NONNULL: // added annotation in parts[2]: bold: buf.append("<b>").append(parts[2].charAt(pos)).append("</b>"); //$NON-NLS-1$ //$NON-NLS-2$ break; default: // removed annotation in parts[1]: strike: buf.append("<del>").append(parts[1].charAt(pos)).append("</del>"); //$NON-NLS-1$ //$NON-NLS-2$ pos--; // char in parts[2] is not yet consumed } // everything else: buf.append(getHtmlRepresentation(parts[2].substring(pos + 1))); buf.append(getHtmlRepresentation(parts[3])); return buf.toString(); } protected abstract void doAnnotateMember(IProgressMonitor monitor) throws CoreException, UnsupportedEncodingException, IOException; } static class ReturnAnnotationRewriteProposal extends SignatureAnnotationChangeProposal { @Override protected void dryRun() { fDryRun= ExternalAnnotationUtil.annotateReturnType(fCurrentAnnotated, fAnnotatedSignature, fMergeStrategy); } @Override protected void doAnnotateMember(IProgressMonitor monitor) throws CoreException, IOException { annotateMethodReturnType(fAffectedTypeName, fAnnotationFile, fSelector, fSignature, fAnnotatedSignature, fMergeStrategy, monitor); } } static class ParameterAnnotationRewriteProposal extends SignatureAnnotationChangeProposal { int fParamIdx; ParameterAnnotationRewriteProposal(int paramIdx) { fParamIdx= paramIdx; } @Override protected void dryRun() { fDryRun= ExternalAnnotationUtil.annotateParameterType(fCurrentAnnotated, fAnnotatedSignature, fParamIdx, fMergeStrategy); } @Override protected void doAnnotateMember(IProgressMonitor monitor) throws CoreException, IOException { annotateMethodParameterType(fAffectedTypeName, fAnnotationFile, fSelector, fSignature, fAnnotatedSignature, fParamIdx, fMergeStrategy, monitor); } } static class FieldAnnotationRewriteProposal extends SignatureAnnotationChangeProposal { @Override protected void dryRun() { fDryRun= ExternalAnnotationUtil.annotateType(fCurrentAnnotated, fAnnotatedSignature, fMergeStrategy); } @Override protected void doAnnotateMember(IProgressMonitor monitor) throws CoreException, UnsupportedEncodingException, IOException { annotateMember(fAffectedTypeName, fAnnotationFile, fSelector, fSignature, fAnnotatedSignature, fMergeStrategy, monitor); } } static class MissingBindingException extends RuntimeException { private static final long serialVersionUID= 1L; ASTNode fNode; MissingBindingException(ASTNode/*Type or TypeParameter or MethodDeclaration*/ node) { fNode= node; } @Override public String getMessage() { // check if compilation may have been aborted due to classpath trouble / unreportable problem: ASTNode cu= ASTNodes.getParent(fNode, ASTNode.COMPILATION_UNIT); if (cu instanceof CompilationUnit) { for (IProblem problem : ((CompilationUnit) cu).getProblems()) { if (problem.getID() == IProblem.IsClassPathCorrect || problem.getOriginatingFileName() == null) return problem.getMessage(); } } switch (fNode.getNodeType()) { case ASTNode.METHOD_DECLARATION: return "Could not resolve method "+fNode.toString(); //$NON-NLS-1$ case ASTNode.VARIABLE_DECLARATION_FRAGMENT: return "Could not resolve field "+fNode.toString(); //$NON-NLS-1$ default: return "Could not resolve type "+fNode.toString(); //$NON-NLS-1$ } } } static ITypeBinding resolveBinding(TypeParameter type) { ITypeBinding binding= type.resolveBinding(); if (binding == null || binding.isRecovered()) throw new MissingBindingException(type); return binding; } static ITypeBinding resolveBinding(Type type) { ITypeBinding binding= type.resolveBinding(); if (binding == null || binding.isRecovered()) throw new MissingBindingException(type); return binding; } static IMethodBinding resolveBinding(MethodDeclaration method) { IMethodBinding binding= method.resolveBinding(); if (binding == null || binding.isRecovered()) throw new MissingBindingException(method); return binding; } static IVariableBinding resolveBinding(VariableDeclaration variable) { IVariableBinding binding= variable.resolveBinding(); if (binding == null || binding.isRecovered()) throw new MissingBindingException(variable); return binding; } /* Quick assist on class file, propose changes on any type detail. */ public static void collectExternalAnnotationProposals(ICompilationUnit cu, ASTNode coveringNode, int offset, ArrayList<IJavaCompletionProposal> resultingCollection) { IJavaProject javaProject= cu.getJavaProject(); if (JavaCore.DISABLED.equals(javaProject.getOption(JavaCore.COMPILER_ANNOTATION_NULL_ANALYSIS, true))) return; if (!hasAnnotationPathInWorkspace(javaProject, cu)) // refuse to update files outside the workspace return; ASTNode inner= null; // the innermost type or type parameter node (to be annotated, unless annotating a dimension) ASTNode outer= null; // will become the outermost type or type parameter node (to be traversed) SingleVariableDeclaration variable= null; // when annotating extra dimension or varars this is where we get that additional info from boolean annotateVarargs= false; int extraDims= 0; // total number of extra dimensions int outerExtraDims= 0; // number of outer extra dimension preceding the annotation position if (coveringNode instanceof Dimension && coveringNode.getLocationInParent() == SingleVariableDeclaration.EXTRA_DIMENSIONS2_PROPERTY) { // annotating extra dimensions, remember dimension counts variable= (SingleVariableDeclaration) coveringNode.getParent(); outer= variable.getType(); inner= variable.getType(); List<?> extraDimensions= variable.extraDimensions(); extraDims= extraDimensions.size(); outerExtraDims= extraDimensions.indexOf(coveringNode); } else if (coveringNode instanceof SingleVariableDeclaration) { // annotating varargs ellipsis? variable= (SingleVariableDeclaration) coveringNode; outer= variable.getType(); inner= variable.getType(); if (variable.isVarargs()) { Type type= variable.getType(); if (offset < type.getStartPosition()+type.getLength()) return; if (offset+3 > variable.getName().getStartPosition()) return; annotateVarargs= true; } else { return; } } else { // annotating 'normal' type? while (true) { if (coveringNode instanceof Type || coveringNode instanceof TypeParameter) { inner= coveringNode; break; } coveringNode= coveringNode.getParent(); if (coveringNode == null) return; } if (inner == null || inner.getNodeType() == ASTNode.PRIMITIVE_TYPE) return; // cannot be annotated outer= inner; ASTNode next; while (((next= outer.getParent()) instanceof Type) || (next instanceof TypeParameter)) outer= next; } // prepare three renderers for three proposals: ASTNode typeToAnnotate = (!annotateVarargs && extraDims == 0) ? inner : null; TypeRenderer rendererNonNull= new TypeRenderer(typeToAnnotate, offset, NONNULL); TypeRenderer rendererNullable= new TypeRenderer(typeToAnnotate, offset, NULLABLE); TypeRenderer rendererRemove= new TypeRenderer(typeToAnnotate, offset, NO_ANNOTATION); if (variable != null) { // prepend dimensions which are not covered by type traversal below if (variable.isVarargs()) { rendererNonNull.addDimension(annotateVarargs); rendererNullable.addDimension(annotateVarargs); rendererRemove.addDimension(annotateVarargs); } for (int i= 0; i < extraDims; i++) { rendererNonNull.addDimension(i == outerExtraDims); rendererNullable.addDimension(i == outerExtraDims); rendererRemove.addDimension(i == outerExtraDims); } } boolean useJava8= JavaModelUtil.is18OrHigher(javaProject.getOption(JavaCore.COMPILER_SOURCE, true)); if (!useJava8 && (outer != inner || outerExtraDims > 0)) { // below 1.8 we can only annotate the top type (not type parameter) // still need to handle ParameterizedType (outer) with SimpleType (inner) if (!(outer.getNodeType() == ASTNode.PARAMETERIZED_TYPE && inner.getParent() == outer)) return; } try { if (outer instanceof Type) { if (extraDims == 0 && !annotateVarargs) { ITypeBinding typeBinding= resolveBinding((Type) outer); if (typeBinding.isPrimitive()) return; } outer.accept(rendererNonNull); outer.accept(rendererNullable); outer.accept(rendererRemove); } else { // type parameter List<?> siblingList= (List<?>) outer.getParent().getStructuralProperty(outer.getLocationInParent()); rendererNonNull.visitTypeParameters(siblingList); rendererNullable.visitTypeParameters(siblingList); rendererRemove.visitTypeParameters(siblingList); } StructuralPropertyDescriptor locationInParent= outer.getLocationInParent(); ProposalCreator creator= null; if (locationInParent == MethodDeclaration.RETURN_TYPE2_PROPERTY) { MethodDeclaration method= (MethodDeclaration) ASTNodes.getParent(coveringNode, MethodDeclaration.class); creator= new ReturnProposalCreator(cu, resolveBinding(method)); } else if (locationInParent == SingleVariableDeclaration.TYPE_PROPERTY) { ASTNode param= outer.getParent(); if (param.getLocationInParent() == MethodDeclaration.PARAMETERS_PROPERTY) { MethodDeclaration method= (MethodDeclaration) ASTNodes.getParent(coveringNode, MethodDeclaration.class); int paramIdx= method.parameters().indexOf(param); if (paramIdx != -1) creator= new ParameterProposalCreator(cu, resolveBinding(method), paramIdx); } } else if (locationInParent == FieldDeclaration.TYPE_PROPERTY) { FieldDeclaration field= (FieldDeclaration) ASTNodes.getParent(coveringNode, FieldDeclaration.class); if (field.fragments().size() > 0) { VariableDeclarationFragment fragment= (VariableDeclarationFragment) field.fragments().get(0); creator= new FieldProposalCreator(cu, resolveBinding(fragment)); } } if (creator != null) { createProposalsForType(cu, inner, extraDims, outerExtraDims, annotateVarargs, offset, rendererNonNull, rendererNullable, rendererRemove, creator, resultingCollection); } } catch (MissingBindingException mbe) { JavaPlugin.log(JavaUIStatus.createError(IStatus.ERROR, "Error during computation of Annotate proposals: "+mbe.getMessage(), mbe)); //$NON-NLS-1$ return; } } static boolean hasAnnotationPathInWorkspace(IJavaProject javaProject, ICompilationUnit cu) { IPackageFragmentRoot root= (IPackageFragmentRoot) cu.getAncestor(IJavaElement.PACKAGE_FRAGMENT_ROOT); if (root != null) { try { IClasspathEntry resolvedClasspathEntry= root.getResolvedClasspathEntry(); for (IClasspathAttribute cpa : resolvedClasspathEntry.getExtraAttributes()) { if (IClasspathAttribute.EXTERNAL_ANNOTATION_PATH.equals(cpa.getName())) { Path annotationPath= new Path(cpa.getValue()); IProject project= javaProject.getProject(); if (project.exists(annotationPath)) return true; IWorkspaceRoot wsRoot= project.getWorkspace().getRoot(); return wsRoot.exists(annotationPath); } } } catch (JavaModelException jme) { return false; } } return false; } private static abstract class ProposalCreator { ICompilationUnit fCU; ITypeBinding fDeclaringClass; String fSelector; String fSignature; MergeStrategy fMergeStrategy= MergeStrategy.OVERWRITE_ANNOTATIONS; ProposalCreator(ICompilationUnit cu, ITypeBinding declaringClass, String selector, String signature) { fCU= cu; fDeclaringClass= declaringClass; fSelector= selector; fSignature= signature; } SignatureAnnotationChangeProposal create(String annotatedSignature, String label) { SignatureAnnotationChangeProposal operation= doCreate(annotatedSignature, label); if (!operation.initializeOperation(fCU, fDeclaringClass, fSelector, fSignature, annotatedSignature, label, fMergeStrategy)) return null; return operation; } abstract SignatureAnnotationChangeProposal doCreate(String annotatedSignature, String label); } private static class ReturnProposalCreator extends ProposalCreator { ReturnProposalCreator(ICompilationUnit cu, IMethodBinding methodBinding) { super(cu, methodBinding.getDeclaringClass(), methodBinding.getName(), extractGenericSignature(methodBinding)); } @Override SignatureAnnotationChangeProposal doCreate(String annotatedSignature, String label) { return new ReturnAnnotationRewriteProposal(); } } private static class ParameterProposalCreator extends ProposalCreator { int fParamIdx; ParameterProposalCreator(ICompilationUnit cu, IMethodBinding methodBinding, int paramIdx) { super(cu, methodBinding.getDeclaringClass(), methodBinding.isConstructor() ? CONSTRUCTOR_SELECTOR : methodBinding.getName(), extractGenericSignature(methodBinding)); fParamIdx= paramIdx; } @Override SignatureAnnotationChangeProposal doCreate(String annotatedSignature, String label) { return new ParameterAnnotationRewriteProposal(fParamIdx); } } private static class FieldProposalCreator extends ProposalCreator { FieldProposalCreator(ICompilationUnit cu, IVariableBinding fieldBinding) { super(cu, fieldBinding.getDeclaringClass(), fieldBinding.getName(), extractGenericTypeSignature(fieldBinding.getType())); } @Override SignatureAnnotationChangeProposal doCreate(String annotatedSignature, String label) { return new FieldAnnotationRewriteProposal(); } } /* Create one proposal from each of the three given renderers. */ static void createProposalsForType(ICompilationUnit cu, ASTNode type, int dims, int outerDims, boolean annotateVarargs, int offset, TypeRenderer rendererNonNull, TypeRenderer rendererNullable, TypeRenderer rendererRemove, ProposalCreator creator, ArrayList<IJavaCompletionProposal> resultingCollection) { SignatureAnnotationChangeProposal operation; String label; // propose adding @NonNull: label= getAddAnnotationLabel(NullAnnotationsFix.getNonNullAnnotationName(cu, true), type, dims, outerDims, annotateVarargs, offset); operation= creator.create(rendererNonNull.getResult(), label); if (operation != null) resultingCollection.add(operation); // propose adding @Nullable: label= getAddAnnotationLabel(NullAnnotationsFix.getNullableAnnotationName(cu, true), type, dims, outerDims, annotateVarargs, offset); operation= creator.create(rendererNullable.getResult(), label); if (operation != null) resultingCollection.add(operation); // propose removing annotation: label= Messages.format(FixMessages.ExternalNullAnnotationChangeProposals_remove_nullness_annotation, new String[] { type2String(type, offset) }); operation= creator.create(rendererRemove.getResult(), label); if (operation != null) resultingCollection.add(operation); } static String getAddAnnotationLabel(String annotationName, ASTNode type, int dims, int outerDims, boolean annotateVarargs, int offset) { StringBuilder left= null; StringBuilder dimsRight= null; if (type.getNodeType() == ASTNode.ARRAY_TYPE) { // find the insertion point using the text offset: ArrayType arrayType= (ArrayType) type; left= new StringBuilder(arrayType.getElementType().toString()); dimsRight= new StringBuilder(); @SuppressWarnings("rawtypes") List dimensions= arrayType.dimensions(); for (int i= 0; i < dimensions.size(); i++) { Dimension dimension= (Dimension) dimensions.get(i); if (dimension.getStartPosition() + dimension.getLength() <= offset) left.append("[]"); //$NON-NLS-1$ else dimsRight.append("[]"); //$NON-NLS-1$ } } else if (dims > 0) { // find then insertion point using the dimension counts: left= new StringBuilder(type.toString()); dimsRight= new StringBuilder(); for (int i= 0; i < dims; i++) { if (i < outerDims) left.append("[]"); //$NON-NLS-1$ else dimsRight.append("[]"); //$NON-NLS-1$ } } else if (annotateVarargs) { left = new StringBuilder(type.toString()); dimsRight = new StringBuilder(); } if (left != null && dimsRight != null) { if (annotateVarargs) dimsRight.append("..."); //$NON-NLS-1$ // need to assemble special format with annotation attached to the selected dimension: return Messages.format(FixMessages.ExternalNullAnnotationChangeProposals_add_nullness_array_annotation, new String[] { left.toString(), annotationName, dimsRight.toString() }); } return Messages.format(FixMessages.ExternalNullAnnotationChangeProposals_add_nullness_annotation, new String[] { annotationName, type.toString() }); } static String type2String(ASTNode type, int offset) { if (type.getNodeType() == ASTNode.ARRAY_TYPE) { ArrayType arrayType= (ArrayType) type; StringBuilder buf= new StringBuilder(arrayType.getElementType().toString()); @SuppressWarnings("rawtypes") List dimensions= arrayType.dimensions(); for (int i= 0; i < dimensions.size(); i++) { Dimension dimension= (Dimension) dimensions.get(i); if (dimension.getStartPosition() + dimension.getLength() > offset) buf.append("[]"); //$NON-NLS-1$ } return buf.toString(); } return type.toString(); } /** * A visitor that renders an AST snippet representing a type or type parameter. For rendering * the Eclipse External Annotation format is used, i.e., class file signatures with additions * for null annotations. * <p> * In particular a given null annotation is inserted for the given focusType. * </p> */ static class TypeRenderer extends ASTVisitor { StringBuffer fBuffer; ASTNode fFocusType; // Type or TypeParameter int fOffset; char fAnnotation; public TypeRenderer(ASTNode focusType, int offset, char annotation) { fBuffer= new StringBuffer(); fFocusType= focusType; fOffset= offset; fAnnotation= annotation; } public void addDimension(boolean annotate) { fBuffer.append('['); if (annotate) fBuffer.append(fAnnotation); } public String getResult() { return fBuffer.toString(); } /* Renders a type parameter list in angle brackets. */ public void visitTypeParameters(@SuppressWarnings("rawtypes") List parameters) { fBuffer.append('<'); for (Object p : parameters) ((TypeParameter) p).accept(this); fBuffer.append('>'); } @Override public boolean visit(ParameterizedType type) { fBuffer.append('L'); if (type == fFocusType || type.getType() == fFocusType) fBuffer.append(fAnnotation); fBuffer.append(binaryName(resolveBinding(type))); fBuffer.append('<'); for (Object arg : type.typeArguments()) ((Type) arg).accept(this); fBuffer.append('>'); fBuffer.append(';'); return false; } @Override public boolean visit(WildcardType wildcard) { Type bound= wildcard.getBound(); if (bound == null) { fBuffer.append('*'); } else if (wildcard.isUpperBound()) { fBuffer.append('+'); } else { fBuffer.append('-'); } if (wildcard == fFocusType) fBuffer.append(fAnnotation); if (bound != null) bound.accept(this); return false; } @Override public boolean visit(ArrayType array) { @SuppressWarnings("rawtypes") List dimensions= array.dimensions(); boolean annotated= false; for (int i= 0; i < dimensions.size(); i++) { fBuffer.append('['); Dimension dimension= (Dimension) dimensions.get(i); if (!annotated && array == fFocusType && dimension.getStartPosition() + dimension.getLength() > fOffset) { fBuffer.append(fAnnotation); annotated= true; } } array.getElementType().accept(this); return false; } @Override public boolean visit(TypeParameter parameter) { if (parameter == fFocusType) fBuffer.append(fAnnotation); fBuffer.append(parameter.getName().getIdentifier()); Type classBound= null; for (Object bound : parameter.typeBounds()) { Type typeBound= (Type) bound; if (resolveBinding(typeBound).isClass()) { classBound= typeBound; break; } } if (classBound != null) { fBuffer.append(':'); classBound.accept(this); } else { ITypeBinding typeBinding= resolveBinding(parameter); fBuffer.append(":L").append(binaryName(typeBinding.getSuperclass())).append(';'); //$NON-NLS-1$ } for (Object bound : parameter.typeBounds()) { if (bound == classBound) continue; Type typeBound= (Type) bound; fBuffer.append(':'); typeBound.accept(this); } return false; } @Override public boolean visit(SimpleType type) { ITypeBinding typeBinding= resolveBinding(type); if (typeBinding.isTypeVariable()) { fBuffer.append('T'); if (fFocusType == type) fBuffer.append(fAnnotation); fBuffer.append(typeBinding.getName()).append(';'); } else { fBuffer.append('L'); if (fFocusType == type) fBuffer.append(fAnnotation); fBuffer.append(binaryName(typeBinding)).append(';'); } return false; } @Override public boolean visit(PrimitiveType node) { // not a legal focus type, but could be array element type fBuffer.append(resolveBinding(node).getBinaryName()); return false; } String binaryName(ITypeBinding type) { return type.getBinaryName().replace('.', '/'); } } }